package edu.bbu.commonproject.backend.db.services.mongodb;

import java.io.IOException;
import java.util.ArrayList;

import com.mongodb.BasicDBObject;
import com.mongodb.DBCursor;
import com.mongodb.MongoException;
import com.mongodb.gridfs.GridFS;
import com.mongodb.gridfs.GridFSDBFile;
import com.mongodb.gridfs.GridFSInputFile;

import edu.bbu.commonproject.backend.db.exceptions.DataBaseErrorException;
import edu.bbu.commonproject.backend.db.exceptions.FileDownloadException;
import edu.bbu.commonproject.backend.db.exceptions.FileNotExistException;
import edu.bbu.commonproject.backend.db.exceptions.FileUploadException;
import edu.bbu.commonproject.backend.db.factory.mongodb.MongoDBDAOFactory;
import edu.bbu.commonproject.backend.db.model.DBDirectory;
import edu.bbu.commonproject.backend.db.model.DBFile;
import edu.bbu.commonproject.backend.db.services.FileDAOServices;

/**
 * The MOndoDb file services. 
 */
public class MongoDBFileDAOServices implements FileDAOServices
{
	/** The temp file type. */
	private static final String TEMP_TYPE = "temp";
	
	/** The regular file type. */
	private static final String FILE_TYPE = "file";
	
	/**
	 * Uploading a new file into database.
	 */
	public void uploadFile(DBFile file) throws FileUploadException, DataBaseErrorException
	{
		int fileVersion = 0;
		
		try
		{
			GridFS gridFS = new GridFS(MongoDBDAOFactory.dataBase, file.getOwner());
			
			GridFSInputFile inputFile = gridFS.createFile(file.getFileInputStream(), file.getFileName());
			inputFile.put("directory", file.getDirectory());
			
			try
			{
				fileVersion = getLastVersionNumberIncludingTemps(file) + 1;
			}
			catch (FileNotExistException e) 
			{
				fileVersion = 1;
			}
			catch (DataBaseErrorException e)
			{
				throw new FileUploadException("Upload failed, when getting the files version number.");
			}
			
			inputFile.put("version", fileVersion);
			inputFile.put("type", FILE_TYPE);
			
			createTempVersion(file, fileVersion);
			
			inputFile.save();
			
			deleteTempVersion(file, fileVersion);
		}
		catch (MongoException e)
		{
			deleteTempVersion(file, fileVersion);
			throw new DataBaseErrorException("Error occured on database, when uploading a file.");
		}
	}
	
	/**
	 * Createint a temp version of a file.
	 * @param file the file model.
	 * @param version the file version.
	 * @throws DataBaseErrorException if error occured in database.
	 */
	private void createTempVersion(DBFile file, int version) throws DataBaseErrorException
	{
		try
		{
			BasicDBObject tempFileVersion = new BasicDBObject();
			tempFileVersion.put("directory", file.getDirectory());
			tempFileVersion.put("filename", file.getFileName());
			tempFileVersion.put("version", version);
			tempFileVersion.put("type", TEMP_TYPE);
			
			MongoDBDAOFactory.dataBase.getCollection(file.getOwner() + ".files").insert(tempFileVersion);
		}
		catch (MongoException e)
		{
			throw new DataBaseErrorException("Error occured on database, when creating a temp file version.");
		}
	}
	
	/**
	 * Delete temp file version.
	 * @param file the file model.
	 * @param version the file version.
	 * @throws DataBaseErrorException if error occured in database.
	 */
	private void deleteTempVersion(DBFile file, int version) throws DataBaseErrorException
	{
		try
		{			
			BasicDBObject tempFileVersion = new BasicDBObject();
			tempFileVersion.put("directory", file.getDirectory());
			tempFileVersion.put("filename", file.getFileName());
			tempFileVersion.put("version", version);
			tempFileVersion.put("type", TEMP_TYPE);
			
			MongoDBDAOFactory.dataBase.getCollection(file.getOwner() + ".files").remove(tempFileVersion);
		}
		catch (MongoException e)
		{
			throw new DataBaseErrorException("Error occured on database, on file delete.");
		}
	}

	/**
	 * Downloading a file from the database.
	 */
	public void downloadFile(DBFile file) throws FileNotExistException, FileDownloadException, DataBaseErrorException
	{
		try
		{
			GridFS gridFS = new GridFS(MongoDBDAOFactory.dataBase, file.getOwner());
			
			BasicDBObject querryDbObject = new BasicDBObject();
			querryDbObject.put("filename", file.getFileName());
			querryDbObject.put("directory", file.getDirectory());
			querryDbObject.put("version", file.getVersion());
			querryDbObject.put("type", FILE_TYPE);
			
			GridFSDBFile outputFile = gridFS.findOne(querryDbObject);
			
			if(outputFile != null)
			{
				outputFile.writeTo(file.getFileOutputStream());
			}
			else 
			{
				throw new FileNotExistException("File already deleted from database.");
			}
		}
		catch (IOException e)
		{
			throw new FileDownloadException("File download failed, the connection dropped down.");
		}
		catch(MongoException e)
		{
			throw new DataBaseErrorException("Error occured on database, when downloading a file.");
		}
	}
	
	/**
	 * Getting the version numbers including temp files too.
	 * @param file the file model.
	 * @return the last version number.
	 * @throws FileNotExistException if the file not exists.
	 * @throws DataBaseErrorException if error occured in database.
	 */
	private int getLastVersionNumberIncludingTemps(DBFile file) throws FileNotExistException, DataBaseErrorException
	{
		try
		{
			int versionNumber = -1;
			int maxVersionNumber = -1;
			
			GridFS gridFS = new GridFS(MongoDBDAOFactory.dataBase, file.getOwner());
			
			BasicDBObject querryDbObject = new BasicDBObject();
			querryDbObject.put("filename", file.getFileName());
			querryDbObject.put("directory", file.getDirectory());
			
			DBCursor cursor = gridFS.getFileList(querryDbObject);
			
			while(cursor.hasNext())
			{
				versionNumber = (int) cursor.next().get("version");
				
				if(versionNumber > maxVersionNumber)
				{
					maxVersionNumber = versionNumber;
				}
			}
			
			if(maxVersionNumber == -1)
			{
				throw new FileNotExistException("File already deleted from database.");
			}
			
			return maxVersionNumber;
		}
		catch (MongoException e)
		{
			throw new DataBaseErrorException("Error occured on database, when getting the last version number.");
		}
	}
	
	/**
	 * Getting the last version number. 
	 */
	public int getLastVersionNumber(DBFile file) throws FileNotExistException, DataBaseErrorException
	{
		try
		{
			int versionNumber = -1;
			int maxVersionNumber = -1;
			
			GridFS gridFS = new GridFS(MongoDBDAOFactory.dataBase, file.getOwner());
			
			BasicDBObject querryDbObject = new BasicDBObject();
			querryDbObject.put("filename", file.getFileName());
			querryDbObject.put("directory", file.getDirectory());
			querryDbObject.put("type", FILE_TYPE);
			
			DBCursor cursor = gridFS.getFileList(querryDbObject);
			
			while(cursor.hasNext())
			{
				versionNumber = (int) cursor.next().get("version");
				
				if(versionNumber > maxVersionNumber)
				{
					maxVersionNumber = versionNumber;
				}
			}
			
			if(maxVersionNumber == -1)
			{
				throw new FileNotExistException("File already deleted from database.");
			}
			
			return maxVersionNumber;
		}
		catch (MongoException e)
		{
			throw new DataBaseErrorException("Error occured on database, when getting the last version number.");
		}
	}
	
	/**
	 * Getting the number of versions of a file. 
	 */
	public int getNumberOFVersions(DBFile file) throws FileNotExistException, DataBaseErrorException
	{
		try
		{
			int versionNumbers = 0;
			
			GridFS gridFS = new GridFS(MongoDBDAOFactory.dataBase, file.getOwner());
			
			BasicDBObject querryDbObject = new BasicDBObject();
			querryDbObject.put("filename", file.getFileName());
			querryDbObject.put("directory", file.getDirectory());
			querryDbObject.put("type", FILE_TYPE);
			
			DBCursor cursor = gridFS.getFileList(querryDbObject);
			
			while(cursor.hasNext())
			{
				versionNumbers++;
			}
			
			if(versionNumbers == 0)
			{
				throw new FileNotExistException("File already deleted from database.");
			}
			
			return versionNumbers;
		}
		catch (MongoException e)
		{
			throw new DataBaseErrorException("Error occured on database, when getting the number of versions.");
		}
	}
	
	/**
	 * Getting the version numbers. 
	 */
	public ArrayList<Integer> getVersionNumbers(DBFile file) throws FileNotExistException, DataBaseErrorException
	{
		try
		{			
			GridFS gridFS = new GridFS(MongoDBDAOFactory.dataBase, file.getOwner());
			
			BasicDBObject querryDbObject = new BasicDBObject();
			querryDbObject.put("filename", file.getFileName());
			querryDbObject.put("directory", file.getDirectory());
			querryDbObject.put("type", FILE_TYPE);
			
			DBCursor cursor = gridFS.getFileList(querryDbObject);
			
			ArrayList<Integer> versionList = new ArrayList<Integer>();
			
			while(cursor.hasNext())
			{
				versionList.add((int) cursor.next().get("version"));
			}
			
			if(versionList.size() == 0)
			{
				throw new FileNotExistException("File already deleted from database.");
			}
			
			return versionList;
			
		}
		catch (MongoException e)
		{
			throw new DataBaseErrorException("Error occured on database, when getting the version numbers.");
		}
	}
	
	/**
	 * Deleting a file from the database.
	 */
	public void deleteFile(DBFile file) throws FileNotExistException, DataBaseErrorException
	{
		try
		{
			GridFS gridFS = new GridFS(MongoDBDAOFactory.dataBase, file.getOwner());
			
			BasicDBObject querryDbObject = new BasicDBObject();
			querryDbObject.put("filename", file.getFileName());
			querryDbObject.put("directory", file.getDirectory());
			querryDbObject.put("version", file.getVersion());
			querryDbObject.put("type", FILE_TYPE);
			
			GridFSDBFile gridFSFile = gridFS.findOne(querryDbObject);
			
			if(gridFSFile != null)
			{
				gridFS.remove(gridFSFile);
			}
			else 
			{
				throw new FileNotExistException("File already deleted from database.");
			}
		}
		catch (MongoException e)
		{
			throw new DataBaseErrorException("Error occured on database, on file delete.");
		}
	}
	
	/**
	 * Delete all of the file versions. 
	 */
	public void deleteAllFileVersions(DBFile file) throws FileNotExistException, DataBaseErrorException
	{
		try
		{
			GridFS gridFS = new GridFS(MongoDBDAOFactory.dataBase, file.getOwner());
			
			ArrayList<Integer> versionList = getVersionNumbers(file);
			
			for (Integer versionNumber : versionList)
			{
				BasicDBObject querryDbObject = new BasicDBObject();
				querryDbObject.put("filename", file.getFileName());
				querryDbObject.put("directory", file.getDirectory());
				querryDbObject.put("version", versionNumber.intValue());
				querryDbObject.put("type", FILE_TYPE);
				
				GridFSDBFile gridFSFile = gridFS.findOne(querryDbObject);
				
				if(gridFSFile != null)
				{
					gridFS.remove(gridFSFile);
				}
				else 
				{
					throw new FileNotExistException("File already deleted from database.");
				}
			}
		}
		catch (MongoException e)
		{
			throw new DataBaseErrorException("Error occured on database, on deleting all of the file versions.");
		}
	}

	/**
	 * Getting the files from a directory. 
	 */
	public ArrayList<String> getFilesFromDirectory(DBDirectory directory) throws DataBaseErrorException
	{
		try
		{
			GridFS gridFS = new GridFS(MongoDBDAOFactory.dataBase, directory.getOwner());
			
			BasicDBObject querryDbObject = new BasicDBObject();
			querryDbObject.put("directory", directory.getParentDirectory() + directory.getDirectoryName() + "\\");
			querryDbObject.put("type", FILE_TYPE);
			DBCursor cursor = gridFS.getFileList(querryDbObject);
			
			ArrayList<String> fileList = new ArrayList<String>();
			
			while(cursor.hasNext())
			{
				String fileName = (String) cursor.next().get("filename");
				
				if(!fileList.contains(fileName))
				{
					fileList.add(fileName);
				}
			}
			
			return fileList;
		}
		catch (MongoException e)
		{
			throw new DataBaseErrorException("Error occured on database, when getting the files from directory.");
		}
	}
	
	/**
	 * Getting the file size in bytes.
	 */
	public long getFileSize(DBFile file) throws FileNotExistException, DataBaseErrorException
	{
		try
		{
			GridFS gridFS = new GridFS(MongoDBDAOFactory.dataBase, file.getOwner());
			
			BasicDBObject querryDbObject = new BasicDBObject();
			querryDbObject.put("filename", file.getFileName());
			querryDbObject.put("directory", file.getDirectory());
			querryDbObject.put("version", file.getVersion());
			querryDbObject.put("type", FILE_TYPE);
			
			GridFSDBFile dbFile = gridFS.findOne(querryDbObject);
						
			if(dbFile != null)
			{
				return dbFile.getLength();
			}
			else 
			{
				throw new FileNotExistException("File already deleted from database.");
			}
		}
		catch (MongoException e)
		{
			throw new DataBaseErrorException("Error occured on database, when getting the file size.");
		}
	}
	
	@Override
	public long getFileUploadDate(DBFile file) throws FileNotExistException, DataBaseErrorException 
	{
		try
		{
			GridFS gridFS = new GridFS(MongoDBDAOFactory.dataBase, file.getOwner());
			
			BasicDBObject querryDbObject = new BasicDBObject();
			querryDbObject.put("filename", file.getFileName());
			querryDbObject.put("directory", file.getDirectory());
			querryDbObject.put("version", file.getVersion());
			querryDbObject.put("type", FILE_TYPE);
			
			GridFSDBFile dbFile = gridFS.findOne(querryDbObject);
						
			if(dbFile != null)
			{
				return dbFile.getUploadDate().getTime();
			}
			else 
			{
				throw new FileNotExistException("File already deleted from database.");
			}
		}
		catch (MongoException e)
		{
			throw new DataBaseErrorException("Error occured on database, when getting the file size.");
		}
	}
	
}
